/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package avl.sv.shared.model.classifier;

import avl.sv.shared.model.vectorPreProcessor.Zscore;
import avl.sv.shared.Sample;
import avl.sv.shared.SampleSetClass;
import ij.gui.MessageDialog;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
import libsvm.svm;
import libsvm.svm_model;
import libsvm.svm_node;
import libsvm.svm_parameter;
import libsvm.svm_print_interface;
import libsvm.svm_problem;

/**
 *
 * @author benbryan
 */
public class ClassifierSVM implements AbstractClassifier, Serializable{
    public String classNames[];
    public boolean isActive = true;
    public String message;
    public Date lastTrained;
    
    public String svm_modelString;
    public String zscoreString;
    public transient Zscore zscore;
    public transient svm_model svmModel;
    public svm_parameter param;
    public boolean autoGamma;
    
    private transient SvmPrintInterface svmPrintInterface = new SvmPrintInterface();
    
    @Override
    public String[] getClassNames() {
        return classNames;
    }

    @Override
    public void setClassNames(String[] classNames) {
        this.classNames = classNames;
    }

    @Override
    public boolean isActive() {
        return isActive;
    }

    @Override
    public void setActive(boolean isActive) {
        this.isActive = isActive;
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public Date getLastTrained() {
        return lastTrained;
    }

    @Override
    public void setLastTrained(Date lastTrained) {
        this.lastTrained = lastTrained;
    }
    
    public ClassifierSVM() {
        param = defaultParams();
    }

    public ClassifierSVM(svm_parameter param, boolean autoGamma) {
        this.param = param;
        this.autoGamma = autoGamma;
    }  
    
    public double adjustGamma(int numelFeatures){
        if (autoGamma){
            param.gamma = 1/(double)numelFeatures;
        }
        return param.gamma;
    }

    public static svm_parameter defaultParams(){
        svm_parameter p = new svm_parameter();
        p.svm_type = svm_parameter.C_SVC;
        p.kernel_type = svm_parameter.RBF;
        p.degree = 3;
        p.gamma = 0.5;
        p.coef0 = 0;
        p.nu = 0.5;
        p.cache_size = 200;
        p.C = 1;
        p.eps = 1e-3;
        p.p = 0.1;
        p.shrinking = 1;
        p.probability = 0;
        p.nr_weight = 0;
        p.weight_label = new int[0];
        p.weight = new double[0];
        return p;
    }    

    public String zscoreToString(){
        if (zscore == null){
            return zscoreString;
        }
        
        StringBuilder sb = new StringBuilder();
        sb.append("mean=");
        for (double m:zscore.mean){
            sb.append(String.valueOf(m));
            sb.append(",");
        }
        sb.setLength(sb.length()-1);
        sb.append(";");
        sb.append("std=");
        for (double s:zscore.std){
            sb.append(String.valueOf(s));
            sb.append(",");
        }
        sb.setLength(sb.length()-1);
        return sb.toString();
    }
    
    public boolean setZscore(String full){
        if (full == null){
            return false;
        }
        if ("".equals(full)){
            return false;
        }
        String meanStrings[] = null;
        String stdStrings[] = null;
        for(String s:full.split(";")){
            int idx;
            idx = s.indexOf("mean=");
            if (idx>=0){
                String sub = s.substring(idx+5);
                meanStrings = sub.split(",");
            } 
            idx = s.indexOf("std=");
            if (idx>=0){
                String sub = s.substring(idx+4);
                stdStrings = sub.split(",");
            } 
        }
        
        if (meanStrings.length != stdStrings.length){
            return false;
        }
        int numel = meanStrings.length;
        double mean[] = new double[numel];
        double std[] = new double[numel];       
        for (int i = 0; i < numel; i++){
            mean[i] = Double.valueOf(meanStrings[i]);
            std[i] = Double.valueOf(stdStrings[i]);
        }
        zscore = new Zscore(mean, std);      
        return true;
    }
        
    public boolean setSvmModel(String model){
        if (model == null){
            return false;
        }
        if ("".equals(model)){
            return false;
        }
        try {
            StringReader sr = new StringReader(model);
            BufferedReader br = new BufferedReader(sr);
            svmModel = svm.svm_load_model(br);
        } catch (IOException ex) {
            Logger.getLogger(ClassifierSVM.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        return true;        
    }
    
    public String svmModelToString(){
        String out = null;
        if (svmModel == null){
            return svm_modelString;
        }
        try {
            File f = new File("temp" + String.valueOf(Math.random())+".txt");
            svm.svm_save_model(f.getAbsolutePath(), svmModel);
            out = readFile(f);
            f.delete();
        } catch (IOException ex) {
            Logger.getLogger(ClassifierSVM.class.getName()).log(Level.SEVERE, null, ex);
        }
        return out;
    }
    
    protected String readFile(File file) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(file));
        String line;
        StringBuilder stringBuilder = new StringBuilder();
        String ls = System.getProperty("line.separator");

        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
            stringBuilder.append(ls);
        }

        return stringBuilder.toString();
    }
    
    private svm_problem makeSVMProblem(ArrayList<SampleSetClass> samplesSets) {
        int numelSamples = 0;
        int numelFeatures = samplesSets.get(0).samples.get(0).featureVector.length;
        for (SampleSetClass samplesSet : samplesSets) {
            numelSamples += samplesSet.samples.size();
        }
        svm_problem prob = new svm_problem();
        prob.l = numelSamples;
        prob.y = new double[prob.l];
        prob.x = new svm_node[prob.l][numelFeatures];
        int sIdx = 0;
        for (int setIdx = 0; setIdx < samplesSets.size(); setIdx++) {
            SampleSetClass samplesSet = samplesSets.get(setIdx);
            ArrayList<Sample> samples = samplesSet.samples;
            for (int sampleIdx = 0; sampleIdx < samples.size(); sampleIdx++) {
                double features[] = samples.get(sampleIdx).featureVector;
                for (int i = 0; i < features.length; i++) {
                    prob.x[sIdx][i] = new svm_node();
                    prob.x[sIdx][i].index = i;
                    prob.x[sIdx][i].value = features[i];
                    prob.y[sIdx] = setIdx;
                }
                sIdx++;
            }
        }
        return prob;
    }
    
    @Override
    public double[] getLabels(){
        int tempInt[]= svmModel.label;
        double[] tempDouble = new double[tempInt.length];
        for (int i = 0; i < tempInt.length; i++){
            tempDouble[i] = tempInt[i];
        }
        return tempDouble;
    }
    
    @Override
    public void train(ArrayList<SampleSetClass> sampleSets) {
        svm.svm_set_print_string_function(svmPrintInterface);
        svmPrintInterface.s = "";
        zscore = new Zscore(sampleSets);
        for (SampleSetClass sampleSet:sampleSets){
            zscore.apply(sampleSet.samples);
        }
        svm_problem prob = makeSVMProblem(sampleSets);
        adjustGamma(sampleSets.get(0).samples.get(0).featureVector.length);
        String check = svm.svm_check_parameter(prob, param);
        if (check != null){
            MessageDialog md = new MessageDialog(null, "SVM Parameter Error", check);
        }
        svmModel = svm.svm_train(prob, param);
        
        int numelClasses = sampleSets.size();
        classNames = new String[numelClasses];
        for (int i = 0; i < numelClasses; i++) {
            classNames[i] = sampleSets.get(i).className;
        }
        zscoreString = zscoreToString();
        svm_modelString = svmModelToString();
        message = svmPrintInterface.s;
    }

    @Override
    public JPanel getOptionsPanel() {
        OptionsPromptSVM prompt = new OptionsPromptSVM(this);
        return prompt;
    }

    @Override
    public String getName() {
        return "SVM";
    }

    @Override
    public void invalidate() {
        svm_modelString = null;
        zscoreString = null;
        zscore = null;
        svmModel = null;
    }
  
    private class SvmPrintInterface implements svm_print_interface{
        public String s;
        @Override
        public void print(String string) {
            s = s+string;
        }
    }
    
    @Override
    public void predict(ArrayList<Sample> samples) {
        if ((svmModel == null) || (zscore == null)) {
            setSvmModel(svm_modelString);
            setZscore(zscoreString);
        }
        if ((svmModel == null) || (zscore == null)) {
            return;
        }        
        zscore.apply(samples);
        int numelFeatures = samples.get(0).featureVector.length;
        for (Sample sample : samples) {
            svm_node[] node = new svm_node[numelFeatures];
            for (int i = 0; i < numelFeatures; i++) {
                node[i] = new svm_node();
                node[i].index = i;
                node[i].value = sample.featureVector[i];
            }
            sample.classifierLabel = svm.svm_predict(svmModel, node);
        }
    }   
    
    @Override
    public boolean isValid() {
        if ((svmModel != null) && (zscore != null)){
            return true;
        }
        if (svmModel == null){
            if (svm_modelString != null){
                setSvmModel(svm_modelString);
            }
        }
        if (zscore == null){
            if (zscoreString != null){
                setZscore(zscoreString);
            }
        }
        if ((svmModel != null) && (zscore != null)){
            return true;
        }
        return false;
    }
    
}
